/*
 * Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.jmx.remote.security;

import com.sun.jmx.mbeanserver.GetPropertyAction;
import java.io.ObjectInputStream;
import java.security.AccessController;
import java.util.Set;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.NotCompliantMBeanException;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.OperationsException;
import javax.management.QueryExp;
import javax.management.ReflectionException;
import javax.management.loading.ClassLoaderRepository;
import javax.management.remote.MBeanServerForwarder;

/**
 * <p>An object of this class implements the MBeanServer interface
 * and, for each of its methods, calls an appropriate checking method
 * and then forwards the request to a wrapped MBeanServer object.  The
 * checking method may throw a RuntimeException if the operation is
 * not allowed; in this case the request is not forwarded to the
 * wrapped object.</p>
 *
 * <p>A typical use of this class is to insert it between a connector server
 * such as the RMI connector and the MBeanServer with which the connector
 * is associated.  Requests from the connector client can then be filtered
 * and those operations that are not allowed, or not allowed in a particular
 * context, can be rejected by throwing a <code>SecurityException</code>
 * in the corresponding <code>check*</code> method.</p>
 *
 * <p>This is an abstract class, because in its implementation none of
 * the checking methods does anything.  To be useful, it must be
 * subclassed and at least one of the checking methods overridden to
 * do some checking.  Some or all of the MBeanServer methods may also
 * be overridden, for instance if the default checking behavior is
 * inappropriate.</p>
 *
 * <p>If there is no SecurityManager, then the access controller will refuse
 * to create an MBean that is a ClassLoader, which includes MLets, or to
 * execute the method addURL on an MBean that is an MLet. This prevents
 * people from opening security holes unintentionally. Otherwise, it
 * would not be obvious that granting write access grants the ability to
 * download and execute arbitrary code in the target MBean server. Advanced
 * users who do want the ability to use MLets are presumably advanced enough
 * to handle policy files and security managers.</p>
 */
public abstract class MBeanServerAccessController
    implements MBeanServerForwarder {

  public MBeanServer getMBeanServer() {
    return mbs;
  }

  public void setMBeanServer(MBeanServer mbs) {
    if (mbs == null) {
      throw new IllegalArgumentException("Null MBeanServer");
    }
    if (this.mbs != null) {
      throw new IllegalArgumentException("MBeanServer object already " +
          "initialized");
    }
    this.mbs = mbs;
  }

  /**
   * Check if the caller can do read operations. This method does
   * nothing if so, otherwise throws SecurityException.
   */
  protected abstract void checkRead();

  /**
   * Check if the caller can do write operations.  This method does
   * nothing if so, otherwise throws SecurityException.
   */
  protected abstract void checkWrite();

  /**
   * Check if the caller can create the named class.  The default
   * implementation of this method calls {@link #checkWrite()}.
   */
  protected void checkCreate(String className) {
    checkWrite();
  }

  /**
   * Check if the caller can unregister the named MBean.  The default
   * implementation of this method calls {@link #checkWrite()}.
   */
  protected void checkUnregister(ObjectName name) {
    checkWrite();
  }

  //--------------------------------------------
  //--------------------------------------------
  //
  // Implementation of the MBeanServer interface
  //
  //--------------------------------------------
  //--------------------------------------------

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public void addNotificationListener(ObjectName name,
      NotificationListener listener,
      NotificationFilter filter,
      Object handback)
      throws InstanceNotFoundException {
    checkRead();
    getMBeanServer().addNotificationListener(name, listener,
        filter, handback);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public void addNotificationListener(ObjectName name,
      ObjectName listener,
      NotificationFilter filter,
      Object handback)
      throws InstanceNotFoundException {
    checkRead();
    getMBeanServer().addNotificationListener(name, listener,
        filter, handback);
  }

  /**
   * Call <code>checkCreate(className)</code>, then forward this method to the
   * wrapped object.
   */
  public ObjectInstance createMBean(String className, ObjectName name)
      throws
      ReflectionException,
      InstanceAlreadyExistsException,
      MBeanRegistrationException,
      MBeanException,
      NotCompliantMBeanException {
    checkCreate(className);
    SecurityManager sm = System.getSecurityManager();
    if (sm == null) {
      Object object = getMBeanServer().instantiate(className);
      checkClassLoader(object);
      return getMBeanServer().registerMBean(object, name);
    } else {
      return getMBeanServer().createMBean(className, name);
    }
  }

  /**
   * Call <code>checkCreate(className)</code>, then forward this method to the
   * wrapped object.
   */
  public ObjectInstance createMBean(String className, ObjectName name,
      Object params[], String signature[])
      throws
      ReflectionException,
      InstanceAlreadyExistsException,
      MBeanRegistrationException,
      MBeanException,
      NotCompliantMBeanException {
    checkCreate(className);
    SecurityManager sm = System.getSecurityManager();
    if (sm == null) {
      Object object = getMBeanServer().instantiate(className,
          params,
          signature);
      checkClassLoader(object);
      return getMBeanServer().registerMBean(object, name);
    } else {
      return getMBeanServer().createMBean(className, name,
          params, signature);
    }
  }

  /**
   * Call <code>checkCreate(className)</code>, then forward this method to the
   * wrapped object.
   */
  public ObjectInstance createMBean(String className,
      ObjectName name,
      ObjectName loaderName)
      throws
      ReflectionException,
      InstanceAlreadyExistsException,
      MBeanRegistrationException,
      MBeanException,
      NotCompliantMBeanException,
      InstanceNotFoundException {
    checkCreate(className);
    SecurityManager sm = System.getSecurityManager();
    if (sm == null) {
      Object object = getMBeanServer().instantiate(className,
          loaderName);
      checkClassLoader(object);
      return getMBeanServer().registerMBean(object, name);
    } else {
      return getMBeanServer().createMBean(className, name, loaderName);
    }
  }

  /**
   * Call <code>checkCreate(className)</code>, then forward this method to the
   * wrapped object.
   */
  public ObjectInstance createMBean(String className,
      ObjectName name,
      ObjectName loaderName,
      Object params[],
      String signature[])
      throws
      ReflectionException,
      InstanceAlreadyExistsException,
      MBeanRegistrationException,
      MBeanException,
      NotCompliantMBeanException,
      InstanceNotFoundException {
    checkCreate(className);
    SecurityManager sm = System.getSecurityManager();
    if (sm == null) {
      Object object = getMBeanServer().instantiate(className,
          loaderName,
          params,
          signature);
      checkClassLoader(object);
      return getMBeanServer().registerMBean(object, name);
    } else {
      return getMBeanServer().createMBean(className, name, loaderName,
          params, signature);
    }
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  @Deprecated
  public ObjectInputStream deserialize(ObjectName name, byte[] data)
      throws InstanceNotFoundException, OperationsException {
    checkRead();
    return getMBeanServer().deserialize(name, data);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  @Deprecated
  public ObjectInputStream deserialize(String className, byte[] data)
      throws OperationsException, ReflectionException {
    checkRead();
    return getMBeanServer().deserialize(className, data);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  @Deprecated
  public ObjectInputStream deserialize(String className,
      ObjectName loaderName,
      byte[] data)
      throws
      InstanceNotFoundException,
      OperationsException,
      ReflectionException {
    checkRead();
    return getMBeanServer().deserialize(className, loaderName, data);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public Object getAttribute(ObjectName name, String attribute)
      throws
      MBeanException,
      AttributeNotFoundException,
      InstanceNotFoundException,
      ReflectionException {
    checkRead();
    return getMBeanServer().getAttribute(name, attribute);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public AttributeList getAttributes(ObjectName name, String[] attributes)
      throws InstanceNotFoundException, ReflectionException {
    checkRead();
    return getMBeanServer().getAttributes(name, attributes);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public ClassLoader getClassLoader(ObjectName loaderName)
      throws InstanceNotFoundException {
    checkRead();
    return getMBeanServer().getClassLoader(loaderName);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public ClassLoader getClassLoaderFor(ObjectName mbeanName)
      throws InstanceNotFoundException {
    checkRead();
    return getMBeanServer().getClassLoaderFor(mbeanName);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public ClassLoaderRepository getClassLoaderRepository() {
    checkRead();
    return getMBeanServer().getClassLoaderRepository();
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public String getDefaultDomain() {
    checkRead();
    return getMBeanServer().getDefaultDomain();
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public String[] getDomains() {
    checkRead();
    return getMBeanServer().getDomains();
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public Integer getMBeanCount() {
    checkRead();
    return getMBeanServer().getMBeanCount();
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public MBeanInfo getMBeanInfo(ObjectName name)
      throws
      InstanceNotFoundException,
      IntrospectionException,
      ReflectionException {
    checkRead();
    return getMBeanServer().getMBeanInfo(name);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public ObjectInstance getObjectInstance(ObjectName name)
      throws InstanceNotFoundException {
    checkRead();
    return getMBeanServer().getObjectInstance(name);
  }

  /**
   * Call <code>checkCreate(className)</code>, then forward this method to the
   * wrapped object.
   */
  public Object instantiate(String className)
      throws ReflectionException, MBeanException {
    checkCreate(className);
    return getMBeanServer().instantiate(className);
  }

  /**
   * Call <code>checkCreate(className)</code>, then forward this method to the
   * wrapped object.
   */
  public Object instantiate(String className,
      Object params[],
      String signature[])
      throws ReflectionException, MBeanException {
    checkCreate(className);
    return getMBeanServer().instantiate(className, params, signature);
  }

  /**
   * Call <code>checkCreate(className)</code>, then forward this method to the
   * wrapped object.
   */
  public Object instantiate(String className, ObjectName loaderName)
      throws ReflectionException, MBeanException, InstanceNotFoundException {
    checkCreate(className);
    return getMBeanServer().instantiate(className, loaderName);
  }

  /**
   * Call <code>checkCreate(className)</code>, then forward this method to the
   * wrapped object.
   */
  public Object instantiate(String className, ObjectName loaderName,
      Object params[], String signature[])
      throws ReflectionException, MBeanException, InstanceNotFoundException {
    checkCreate(className);
    return getMBeanServer().instantiate(className, loaderName,
        params, signature);
  }

  /**
   * Call <code>checkWrite()</code>, then forward this method to the
   * wrapped object.
   */
  public Object invoke(ObjectName name, String operationName,
      Object params[], String signature[])
      throws
      InstanceNotFoundException,
      MBeanException,
      ReflectionException {
    checkWrite();
    checkMLetMethods(name, operationName);
    return getMBeanServer().invoke(name, operationName, params, signature);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public boolean isInstanceOf(ObjectName name, String className)
      throws InstanceNotFoundException {
    checkRead();
    return getMBeanServer().isInstanceOf(name, className);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public boolean isRegistered(ObjectName name) {
    checkRead();
    return getMBeanServer().isRegistered(name);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
    checkRead();
    return getMBeanServer().queryMBeans(name, query);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
    checkRead();
    return getMBeanServer().queryNames(name, query);
  }

  /**
   * Call <code>checkWrite()</code>, then forward this method to the
   * wrapped object.
   */
  public ObjectInstance registerMBean(Object object, ObjectName name)
      throws
      InstanceAlreadyExistsException,
      MBeanRegistrationException,
      NotCompliantMBeanException {
    checkWrite();
    return getMBeanServer().registerMBean(object, name);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public void removeNotificationListener(ObjectName name,
      NotificationListener listener)
      throws InstanceNotFoundException, ListenerNotFoundException {
    checkRead();
    getMBeanServer().removeNotificationListener(name, listener);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public void removeNotificationListener(ObjectName name,
      NotificationListener listener,
      NotificationFilter filter,
      Object handback)
      throws InstanceNotFoundException, ListenerNotFoundException {
    checkRead();
    getMBeanServer().removeNotificationListener(name, listener,
        filter, handback);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public void removeNotificationListener(ObjectName name,
      ObjectName listener)
      throws InstanceNotFoundException, ListenerNotFoundException {
    checkRead();
    getMBeanServer().removeNotificationListener(name, listener);
  }

  /**
   * Call <code>checkRead()</code>, then forward this method to the
   * wrapped object.
   */
  public void removeNotificationListener(ObjectName name,
      ObjectName listener,
      NotificationFilter filter,
      Object handback)
      throws InstanceNotFoundException, ListenerNotFoundException {
    checkRead();
    getMBeanServer().removeNotificationListener(name, listener,
        filter, handback);
  }

  /**
   * Call <code>checkWrite()</code>, then forward this method to the
   * wrapped object.
   */
  public void setAttribute(ObjectName name, Attribute attribute)
      throws
      InstanceNotFoundException,
      AttributeNotFoundException,
      InvalidAttributeValueException,
      MBeanException,
      ReflectionException {
    checkWrite();
    getMBeanServer().setAttribute(name, attribute);
  }

  /**
   * Call <code>checkWrite()</code>, then forward this method to the
   * wrapped object.
   */
  public AttributeList setAttributes(ObjectName name,
      AttributeList attributes)
      throws InstanceNotFoundException, ReflectionException {
    checkWrite();
    return getMBeanServer().setAttributes(name, attributes);
  }

  /**
   * Call <code>checkUnregister()</code>, then forward this method to the
   * wrapped object.
   */
  public void unregisterMBean(ObjectName name)
      throws InstanceNotFoundException, MBeanRegistrationException {
    checkUnregister(name);
    getMBeanServer().unregisterMBean(name);
  }

  //----------------
  // PRIVATE METHODS
  //----------------

  private void checkClassLoader(Object object) {
    if (object instanceof ClassLoader) {
      throw new SecurityException("Access denied! Creating an " +
          "MBean that is a ClassLoader " +
          "is forbidden unless a security " +
          "manager is installed.");
    }
  }

  private void checkMLetMethods(ObjectName name, String operation)
      throws InstanceNotFoundException {
    // Check if security manager installed
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
      return;
    }
    // Check for addURL and getMBeansFromURL methods
    if (!operation.equals("addURL") &&
        !operation.equals("getMBeansFromURL")) {
      return;
    }
    // Check if MBean is instance of MLet
    if (!getMBeanServer().isInstanceOf(name,
        "javax.management.loading.MLet")) {
      return;
    }
    // Throw security exception
    if (operation.equals("addURL")) { // addURL
      throw new SecurityException("Access denied! MLet method addURL " +
          "cannot be invoked unless a security manager is installed.");
    } else { // getMBeansFromURL
      // Whether or not calling getMBeansFromURL is allowed is controlled
      // by the value of the "jmx.remote.x.mlet.allow.getMBeansFromURL"
      // system property. If the value of this property is true, calling
      // the MLet's getMBeansFromURL method is allowed. The default value
      // for this property is false.
      final String propName = "jmx.remote.x.mlet.allow.getMBeansFromURL";
      GetPropertyAction propAction = new GetPropertyAction(propName);
      String propValue = AccessController.doPrivileged(propAction);
      boolean allowGetMBeansFromURL = "true".equalsIgnoreCase(propValue);
      if (!allowGetMBeansFromURL) {
        throw new SecurityException("Access denied! MLet method " +
            "getMBeansFromURL cannot be invoked unless a " +
            "security manager is installed or the system property " +
            "-Djmx.remote.x.mlet.allow.getMBeansFromURL=true " +
            "is specified.");
      }
    }
  }

  //------------------
  // PRIVATE VARIABLES
  //------------------

  private MBeanServer mbs;
}
